package com.wk.springboot.machine.config;

import com.wk.springboot.machine.common.OrderEvents;
import com.wk.springboot.machine.common.OrderStates;
import com.wk.springboot.machine.entity.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class StateMachineConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    private static final Logger log = LoggerFactory.getLogger(StateMachineConfig.class);

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config)

            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states)

            throws Exception {
        states
                .withStates()
                .initial(OrderStates.NEW)
                .end(OrderStates.COMPLETED)
                .end(OrderStates.CANCELLED)
                .states(EnumSet.allOf(OrderStates.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                .withExternal()
                .source(OrderStates.NEW).target(OrderStates.PAID).event(OrderEvents.PAY)
                .and()
                .withExternal()
                .source(OrderStates.PAID).target(OrderStates.SHIPPED).event(OrderEvents.SHIP)
                .and()
                .withExternal()
                .source(OrderStates.SHIPPED).target(OrderStates.COMPLETED).event(OrderEvents.COMPLETE)
                .and()
                .withExternal()
                .source(OrderStates.NEW).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
                .and()
                .withExternal()
                .source(OrderStates.PAID).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL);
    }

    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<OrderStates, OrderEvents>() {
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("Statechangeto:{}", to.getId());
            }

            @Override
            public void stateMachineError(StateMachine<OrderStates, OrderEvents> stateMachine, Exception exception) {
                log.error("Exceptioncaught:{}", exception.getMessage(), exception);
            }

            @Override
            public void eventNotAccepted(Message<OrderEvents> message) {
                Order order = (Order) message.getHeaders().get("order");
                log.error("Orderstatemachinecan'tchangestate{}-->{}", Objects.requireNonNull(order).getStatus(), message.getPayload());
            }
        };
    }

    @Bean
    public StateMachinePersist<OrderStates, OrderEvents, String> inMemoryStateMachinePersist() {
        return new StateMachinePersist<OrderStates, OrderEvents, String>() {


            private final Map<String, StateMachineContext<OrderStates, OrderEvents>> contexts = new HashMap<>();

            @Override
            public void write(StateMachineContext<OrderStates, OrderEvents> context, String contextObj) {
                contexts.put(contextObj, context);
            }

            @Override
            public StateMachineContext<OrderStates, OrderEvents> read(String contextObj) {
                return contexts.get(contextObj);
            }
        };
    }
}
